﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Renci.iRods.Packets;
using Renci.iRods.Common;
using Renci.iRods.Exceptions;
using Renci.iRods.Linq;

namespace Renci.iRods
{
    [Flags]
    public enum IRodsQueryOption
    {
        None = 0,
        ReturnTotalRowCount = 0x20,
        NoDistinct = 0x40,
        QuotaQuery = 0x80,
    }

    public class IRodsQuery
    {
        private const int _maxRowsPerRequest = 256;

        private readonly Dictionary<DataObjectAttributes, string> _conditions = new Dictionary<DataObjectAttributes, string>();

        private readonly Dictionary<DataObjectAttributes, QueryColumnAction> _outputColumns = new Dictionary<DataObjectAttributes, QueryColumnAction>();

        public int Skip { get; set; }

        public int Take { get; set; }

        public IRodsQueryOption Options { get; set; }

        public IRodsQuery()
        {
            this.Take = 0;
            this.Skip = 0;
        }
        
        public void AddCondition(DataObjectAttributes dataObjectAttribute, string value)
        {
            this._conditions.Add(dataObjectAttribute, value);
        }

        public void AddCondition(DataObjectAttributes dataObjectAttribute, ConditionPredicate predicate, string value)
        {
            switch (predicate)
            {
                case ConditionPredicate.None:
                    if (!this._conditions.ContainsKey(dataObjectAttribute))
                    {
                        this._conditions.Add(dataObjectAttribute, value);
                    }
                    else
                    {
                        throw new NotImplementedException("Applied condition is not supported.");
                    }
                    break;
                case ConditionPredicate.And:
                    if (this._conditions.ContainsKey(dataObjectAttribute))
                    {
                        this._conditions[dataObjectAttribute] = string.Format("{0} && {1}", this._conditions[dataObjectAttribute], value);
                    }
                    else
                    {
                        this._conditions.Add(dataObjectAttribute, value);
                    }
                    break;
                case ConditionPredicate.Or:
                    if (this._conditions.ContainsKey(dataObjectAttribute))
                    {
                        this._conditions[dataObjectAttribute] = string.Format("{0} || {1}", this._conditions[dataObjectAttribute], value);
                    }
                    else
                    {
                        throw new NotImplementedException("Applied condition is not supported.");
                    }
                    break;
                default:
                    break;
            }
        }

        public void AddOrderBy(DataObjectAttributes dataObjectAttribute, QueryColumnAction orderType)
        {
            if (this._outputColumns.ContainsKey(dataObjectAttribute))
            {
                this._outputColumns[dataObjectAttribute] |= orderType;
            }
            else
            {
                this._outputColumns.Add(dataObjectAttribute, QueryColumnAction.Default | orderType);
            }
        }

        public void AddOutputColumn(DataObjectAttributes dataObjectAttribute)
        {
            this.AddOutputColumn(dataObjectAttribute, QueryColumnAction.Default);
        }

        public void AddOutputColumn(DataObjectAttributes dataObjectAttribute, QueryColumnAction columnAction)
        {
            if (this._outputColumns.ContainsKey(dataObjectAttribute))
            {
                this._outputColumns[dataObjectAttribute] |= columnAction;
            }
            else
            {
                this._outputColumns.Add(dataObjectAttribute, columnAction);
            }
        }

        internal GeneralQueryInputPacket GetFirstRequest()
        {
            var take = this.Take;
            if (take > _maxRowsPerRequest)
                take = _maxRowsPerRequest;

            if (take < 1)
                take = _maxRowsPerRequest;

            return new GeneralQueryInputPacket(this._outputColumns, this._conditions, take, this.Skip, IRodsQueryOption.None);
        }

        internal GeneralQueryInputPacket GetNextRequest(int executionId, int numOfRows)
        {
            var take = numOfRows;
            if (take > _maxRowsPerRequest)
                take = _maxRowsPerRequest;

            if (take < 1)
                take = _maxRowsPerRequest;

            return new GeneralQueryInputPacket(this._outputColumns, this._conditions, executionId, take, 0, IRodsQueryOption.None);
        }
    }
}
